using System;
using Moq;
using NUnit.Framework;
using Tycho.Parser.Peg;

namespace Tycho.Parser.Tests.Peg {
    [TestFixture]
    public class ProductionMemoisationFeature {
        private int Index;
        private ParseContext Context;
        private string SourceString;
        private ParseEnvironment ParseEnvironment;
        private ProductionTestExtension Production;
        private ParseResult Result;
        private char[] Source;

        [Test]
        public void ShouldMemoiseNullIfParseFailed () {
            Context = new ParseContext();
            Index = 1;
            SourceString = "blah";
            Result = null;

            SetupParsingEnvironment ();

            Parse ();

            Assert.That (Production.GetMemoTable (Source) [1], Is.Null);
        }

        [Test]
        public void ShouldNotMemoiseLastResultIfResultReturnsSameIndex () {
            Context = new ParseContext();
            Index = 1;
            Result = new ParseResult (Index, null, Context);
            SourceString = "blah";

            SetupParsingEnvironment ();

            Assert.AreEqual(Result, Parse ());

            Assert.IsFalse (Production.GetMemoTable (Source).ContainsKey (1));
        }

        [Test]
        public void ShouldMemoiseLastResult () {
            Context = new ParseContext();
            Index = 0;
            Result = new ParseResult (4, null, Context);
            SourceString = "blah";

            SetupParsingEnvironment ();

            Assert.AreEqual(Result, Parse ());

            Assert.That (Production.GetMemoTable (Source) [0], Is.SameAs (Result));
        }

        [Test]
        public void ShouldNotParseIfResultFoundInMemoTable () {
            Context = new ParseContext();
            Index = 0;
            SourceString = "blah";

            SetupParsingEnvironment ();
            var memoResult = new ParseResult (2, null, Context);
            Production.GetMemoTable (Source) [0] = memoResult;

            Assert.AreEqual(memoResult, Parse ());

            Assert.That (Production.GetMemoTable (Source) [0], Is.SameAs (memoResult));
            Assert.That (Production.NumberOfTimesParsed, Is.EqualTo (0));
        }

        [Test]
        public void ShouldPutIntermediateLeftRecursionResultIntoMemoTableBeforeParse () {
            Context = new ParseContext();
            Index = 0;
            SourceString = "blah";

            bool havePlacedLeftRecursiveResultInMemoTable = false;
            SetupParsingEnvironment (() => {
                havePlacedLeftRecursiveResultInMemoTable = true;
                Assert.That (Production.GetMemoTable (Source) [0], Is.SameAs (ParseEnvironment.LeftRecursionInformation.RecursiveResult));
            });

            Parse ();

            Assert.That (havePlacedLeftRecursiveResultInMemoTable);
        }

        [Test]
        public void ShouldClearMemoTableIfSyntaxErrorThrown () {
            Context = new ParseContext ();
            Index = 0;
            SourceString = "blah";

            SetupParsingEnvironment (() => {
                throw new SyntaxErrorRecoveryException (null, null);
            });

            Assert.That (() => { Parse (); }, Throws.TypeOf(typeof (SyntaxErrorRecoveryException)));

            Assert.False (Production.MemoTable.ContainsKey (0));
            Assert.That (Production.NumberOfTimesParsed, Is.EqualTo (0));
        }

        private void SetupParsingEnvironment () {
            SetupParsingEnvironment(() => { });
        }

        private void SetupParsingEnvironment (Action action) {
            Production = new ProductionTestExtension (Result, action);

            ParseEnvironment = new ParseEnvironment ("filename");
            ParseEnvironment.LeftRecursionInformation = new LeftRecursionInformation ();
            ParseEnvironment.SyntaxErrors = new Mock<ISyntaxErrorLogger> ().Object;
            ParseEnvironment.SyntaxErrorRecoverer = new Mock<ISyntaxErrorRecoverer> ().Object;

            Source = SourceString.ToCharArray ();
        }

        private ParseResult Parse () {
            return Production.Parse (Source, Index, Context, SourceString, ParseEnvironment);
        }
    }
}